package org.offsync.nio

import java.net.Socket
import java.net.SocketAddress
import java.nio.channels.{Selector => JavaSelector}
import java.nio.channels.{SelectionKey => JavaSelectionKey}
import java.nio.channels.{SocketChannel => JavaSocketChannel}
import java.nio.ByteBuffer
import java.net.InetSocketAddress
import org.offsync.rich._
import org.offsync.util.Window

case class SocketChannel(
    protected override val channel: JavaSocketChannel)
    extends SelectableChannel(channel) {
  def connect(
      remote: SocketAddress): Boolean = channel.connect(remote) 

  def finishConnect(): Boolean = channel.finishConnect() 

  def isConnected: Boolean = channel.isConnected
  
  def isConnectionPending: Boolean = channel.isConnectionPending 

  def read(
      dst: ByteBuffer): Int = channel.read(dst)

  def read(
      dsts: Array[ByteBuffer]): Long = channel.read(dsts)

  def read(
      dsts: Array[ByteBuffer],
      offset: Int,
      length: Int): Long = channel.read(dsts, offset, length)

  def socket: Socket = channel.socket

  def write(
    src: ByteBuffer): Int = channel.write(src)

  def write(
    srcs: Array[ByteBuffer]): Long = channel.write(srcs)

  def write(
    srcs: Array[ByteBuffer],
    offset: Int,
    length: Int): Long = channel.write(srcs, offset, length)
    
  def write(
      ioService: IoService,
      window: Window[Byte])(
          f: (() => Int) => Unit): Unit = {
    val writeOnce = onceOnly { result =>
      result match {
        case None => f { () => channel.write(window.toByteBuffer()) }
        case Some(e) => f { () => throw e }
      }
    }
    onCanWrite(ioService)(writeOnce) 
  }

  def read(
      ioService: IoService,
      window: Window[Byte])(
          f: (() => Int) => Unit): Unit = {
    val readOnce = onceOnly { result =>
      result match {
        case None => f { () => channel.read(window.toByteBuffer()) }
        case Some(e) => f { () => throw e }
      }
    }
    onCanRead(ioService)(readOnce)
  }
}

object SocketChannel {
  def open(): SocketChannel = {
    val channel = JavaSocketChannel.open()
    channel.configureBlocking(false)
    return SocketChannel(channel)
  }
  
  def open(remote: SocketAddress): SocketChannel = {
    val channel = JavaSocketChannel.open(remote)
    channel.configureBlocking(false)
    SocketChannel(channel)
  }

  def open(host: String, port: Int): SocketChannel = open(new InetSocketAddress(host, port))
}
